/**
 * https://github.com/hammerjs/touchemulator
 */
var isMultiTouch = false
var multiTouchStartPos
var eventTarget
var isTouchMove = false
var startPosi = {
  x: 0,
  y: 0
}

// polyfills
if (!document.createTouch) {
  document.createTouch = function (view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
    // auto set
    if (clientX == undefined || clientY == undefined) {
      clientX = pageX - window.pageXOffset
      clientY = pageY - window.pageYOffset
    }

    return new Touch(target, identifier, {
      pageX: pageX,
      pageY: pageY,
      screenX: screenX,
      screenY: screenY,
      clientX: clientX,
      clientY: clientY
    })
  }
}

if (!document.createTouchList) {
  document.createTouchList = function () {
    var touchList = new TouchList()
    for (var i = 0; i < arguments.length; i++) {
      touchList[i] = arguments[i]
    }
    touchList.length = arguments.length
    return touchList
  };
}

/**
 * create an touch point
 * @constructor
 * @param target
 * @param identifier
 * @param pos
 * @param deltaX
 * @param deltaY
 * @returns {Object} touchPoint
 */
function Touch(target, identifier, pos, deltaX, deltaY) {
  deltaX = deltaX || 0
  deltaY = deltaY || 0

  this.identifier = identifier
  this.target = target
  this.clientX = pos.clientX + deltaX
  this.clientY = pos.clientY + deltaY
  this.screenX = pos.screenX + deltaX
  this.screenY = pos.screenY + deltaY
  this.pageX = pos.pageX + deltaX
  this.pageY = pos.pageY + deltaY
}

/**
 * create empty touchlist with the methods
 * @constructor
 * @returns touchList
 */
function TouchList() {
  var touchList = []

  touchList.item = function (index) {
    return this[index] || null
  }

  // specified by Mozilla
  touchList.identifiedTouch = function (id) {
    return this[id + 1] || null
  }

  return touchList
}


/**
 * Simple trick to fake touch event support
 * this is enough for most libraries like Modernizr and Hammer
 */
function fakeTouchSupport() {
  var objs = [window, document.documentElement]
  var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend']

  for (var o = 0; o < objs.length; o++) {
    for (var p = 0; p < props.length; p++) {
      if (objs[o] && objs[o][props[p]] == undefined) {
        objs[o][props[p]] = null
      }
    }
  }
}

/**
 * we don't have to emulate on a touch device
 * @returns {boolean}
 */
function hasTouchSupport() {
  return ('ontouchstart' in window) || // touch events
    (window.Modernizr && window.Modernizr.touch) || // modernizr
    (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 2 // pointer events
}

/**
 * disable mouseevents on the page
 * @param ev
 */
function preventMouseEvents(ev) {
  ev.preventDefault()
  ev.stopPropagation()
}

/**
 * only trigger touches when the left mousebutton has been pressed
 * @param touchType
 * @returns {Function}
 */
function onMouse(touchType) {
  return function (ev) {
    if (ev.which !== 1) {
      return
    }

    // 判断是否为touch拖动
    if (touchType === 'touchstart') {
      isTouchMove = false
      startPosi = {
        x: ev.clientX,
        y: ev.clientY
      }
    } else if (touchType === 'touchmove') {
      isTouchMove = isTouchMove || Math.abs(ev.clientX - startPosi.x) > 2 || Math.abs(ev.clientY - startPosi.y) > 2
    }

    // The EventTarget on which the touch point started when it was first placed on the surface,
    // even if the touch point has since moved outside the interactive area of that element.
    // also, when the target doesnt exist anymore, we update it
    if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
      eventTarget = ev.target
    }

    // shiftKey has been lost, so trigger a touchend
    if (isMultiTouch && !ev.shiftKey) {
      triggerTouch('touchend', ev)
      isMultiTouch = false
    }

    triggerTouch(touchType, ev)

    // we're entering the multi-touch mode!
    if (!isMultiTouch && ev.shiftKey) {
      isMultiTouch = true
      multiTouchStartPos = {
        pageX: ev.pageX,
        pageY: ev.pageY,
        clientX: ev.clientX,
        clientY: ev.clientY,
        screenX: ev.screenX,
        screenY: ev.screenY
      }
      triggerTouch('touchstart', ev)
    }

    // reset
    if (ev.type == 'mouseup') {
      multiTouchStartPos = null
      isMultiTouch = false
      eventTarget = null
    }
  }
}

/**
 * trigger a touch event
 * @param eventName
 * @param mouseEv
 */
function triggerTouch(eventName, mouseEv) {
  var touchEvent = document.createEvent('Event')
  touchEvent.initEvent(eventName, true, true)

  touchEvent.altKey = mouseEv.altKey
  touchEvent.ctrlKey = mouseEv.ctrlKey
  touchEvent.metaKey = mouseEv.metaKey
  touchEvent.shiftKey = mouseEv.shiftKey

  touchEvent.touches = getActiveTouches(mouseEv, eventName)
  touchEvent.targetTouches = getActiveTouches(mouseEv, eventName)
  touchEvent.changedTouches = getChangedTouches(mouseEv, eventName)

  eventTarget.dispatchEvent(touchEvent)
}

/**
 * create a touchList based on the mouse event
 * @param mouseEv
 * @returns {TouchList}
 */
function createTouchList(mouseEv) {
  var touchList = new TouchList()

  if (isMultiTouch) {
    var f = TouchEmulator.multiTouchOffset
    var deltaX = multiTouchStartPos.pageX - mouseEv.pageX
    var deltaY = multiTouchStartPos.pageY - mouseEv.pageY

    touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX * -1) - f, (deltaY * -1) + f))
    touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX + f, deltaY - f))
  } else {
    touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0))
  }

  return touchList
}

/**
 * receive all active touches
 * @param mouseEv
 * @returns {TouchList}
 */
function getActiveTouches(mouseEv, eventName) {
  // empty list
  if (mouseEv.type == 'mouseup') {
    return new TouchList()
  }

  var touchList = createTouchList(mouseEv);
  if (isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
    touchList.splice(1, 1)
  }
  return touchList
}

/**
 * receive a filtered set of touches with only the changed pointers
 * @param mouseEv
 * @param eventName
 * @returns {TouchList}
 */
function getChangedTouches(mouseEv, eventName) {
  var touchList = createTouchList(mouseEv)

  // we only want to return the added/removed item on multitouch
  // which is the second pointer, so remove the first pointer from the touchList
  //
  // but when the mouseEv.type is mouseup, we want to send all touches because then
  // no new input will be possible
  if (isMultiTouch && mouseEv.type != 'mouseup' &&
    (eventName == 'touchstart' || eventName == 'touchend')) {
    touchList.splice(0, 1)
  }

  return touchList
}

function preventClick (event) {
  if (isTouchMove) {
    preventMouseEvents(event)
  }
}

/**
 * TouchEmulator initializer
 */
function TouchEmulator() {
  if (hasTouchSupport()) {
    return;
  }

  fakeTouchSupport()

  window.addEventListener('mousedown', onMouse('touchstart'), true)
  window.addEventListener('mousemove', onMouse('touchmove'), true)
  window.addEventListener('mouseup', onMouse('touchend'), true)

  window.addEventListener('mouseenter', preventMouseEvents, true)
  window.addEventListener('mouseleave', preventMouseEvents, true)
  window.addEventListener('mouseout', preventMouseEvents, true)
  window.addEventListener('mouseover', preventMouseEvents, true)
  window.addEventListener('click', preventClick, true)
}

// start distance when entering the multitouch mode
TouchEmulator.multiTouchOffset = 75

export default TouchEmulator
